#include "IntegralLib.hpp"

namespace IntegralLib
{
	template<IsInteger T> int GetBitCount(T val)
	{
		int nBits = 8 * sizeof val;
		int m = 0;
		for (int i = 0; i < nBits; i++)
		{
			if ((T(1) << i) & val) m = i;
		}
		return m + 1;
	}

	template int GetBitCount(int8_t val);
	template int GetBitCount(int16_t val);
	template int GetBitCount(int32_t val);
	template int GetBitCount(int64_t val);
	template int GetBitCount(uint8_t val);
	template int GetBitCount(uint16_t val);
	template int GetBitCount(uint32_t val);
	template int GetBitCount(uint64_t val);

	template<IsInteger T>
	T UIntHash::ROR(T number, int shifts)
	{
		if (shifts < 0) return ROL(number, -shifts);

		constexpr int maxbits = 8 * sizeof number;
		shifts &= maxbits - 1;

		return
			(number >> shifts) |
			(number << (maxbits - shifts));
	}

	template<IsInteger T>
	T UIntHash::ROL(T number, int shifts)
	{
		if (shifts < 0) return ROR(number, -shifts);

		constexpr int maxbits = 8 * sizeof number;
		shifts &= maxbits - 1;

		return
			(number << shifts) |
			(number >> (maxbits - shifts));
	}

	size_t UIntHash::HashInts(size_t hash, const int* ints, size_t numInts, int shifts)
	{
		size_t ret = hash;
		for (size_t i = 0; i < numInts; i++)
		{
			ret = ROL(ret, shifts) + size_t(ints[i]);
		}
		return ret;
	}

	size_t CombineHash(size_t Hash1, size_t Hash2)
	{
		return Hash1 + UIntHash::ROL(Hash2, HashShifts * 2);
	}

	template int8_t  UIntHash::ROL(int8_t number, int shifts);
	template int16_t UIntHash::ROL(int16_t number, int shifts);
	template int32_t UIntHash::ROL(int32_t number, int shifts);
	template int64_t UIntHash::ROL(int64_t number, int shifts);
	template uint8_t  UIntHash::ROL(uint8_t number, int shifts);
	template uint16_t UIntHash::ROL(uint16_t number, int shifts);
	template uint32_t UIntHash::ROL(uint32_t number, int shifts);
	template uint64_t UIntHash::ROL(uint64_t number, int shifts);
	template int8_t  UIntHash::ROR(int8_t number, int shifts);
	template int16_t UIntHash::ROR(int16_t number, int shifts);
	template int32_t UIntHash::ROR(int32_t number, int shifts);
	template int64_t UIntHash::ROR(int64_t number, int shifts);
	template uint8_t  UIntHash::ROR(uint8_t number, int shifts);
	template uint16_t UIntHash::ROR(uint16_t number, int shifts);
	template uint32_t UIntHash::ROR(uint32_t number, int shifts);
	template uint64_t UIntHash::ROR(uint64_t number, int shifts);

	template<IsInteger T>
	T GetGE2N(T number)
	{
		T i = GetLE2N(number);
		if (i < number) i <<= 1;
		return i;
	}

	template<IsInteger T>
	T GetLE2N(T number)
	{
		T i = 1;
		if (!number) return 0;
		return i << (GetBitCount(number) - 1);
	}

	template int8_t  GetGE2N(int8_t number);
	template int16_t GetGE2N(int16_t number);
	template int32_t GetGE2N(int32_t number);
	template int64_t GetGE2N(int64_t number);
	template uint8_t  GetGE2N(uint8_t number);
	template uint16_t GetGE2N(uint16_t number);
	template uint32_t GetGE2N(uint32_t number);
	template uint64_t GetGE2N(uint64_t number);
	template int8_t  GetLE2N(int8_t number);
	template int16_t GetLE2N(int16_t number);
	template int32_t GetLE2N(int32_t number);
	template int64_t GetLE2N(int64_t number);
	template uint8_t  GetLE2N(uint8_t number);
	template uint16_t GetLE2N(uint16_t number);
	template uint32_t GetLE2N(uint32_t number);
	template uint64_t GetLE2N(uint64_t number);

	template<IsInteger T>
	bool Is2N(T number)
	{
		if (!number) return false;
		return (T(1) << (GetBitCount(number) - 1)) == number;
	}

	template bool Is2N(int8_t number);
	template bool Is2N(int16_t number);
	template bool Is2N(int32_t number);
	template bool Is2N(int64_t number);
	template bool Is2N(uint8_t number);
	template bool Is2N(uint16_t number);
	template bool Is2N(uint32_t number);
	template bool Is2N(uint64_t number);

	bool ends_with(const std::string& value, const std::string& ending)
	{
		if (ending.size() > value.size()) return false;
		return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
	}

	std::string tolower(const std::string& s)
	{
		std::string ret = s;
		std::transform(ret.begin(), ret.end(), ret.begin(), [](unsigned char c) { return std::tolower(c); });
		return ret;
	}

	template std::string Join(const std::set<std::string>& Strings, const std::string& Delims);
	template std::string Join(const std::list<std::string>& Strings, const std::string& Delims);
	template std::string Join(const std::vector<std::string>& Strings, const std::string& Delims);
	template std::string Join(const std::unordered_set<std::string>& Strings, const std::string& Delims);

	FastRNG::FastRNG(int seed) : 
		Seed(seed)
	{
	}

	int FastRNG::NextI()
	{
		auto lock = std::scoped_lock(*RNGLock.get());
		Seed = Seed * 0x000343FD + 0x00269EC3;
		return (Seed >> 0x10) & 0x7FFF;
	}

	float FastRNG::NextF()
	{
		return float(NextI()) / float(NextI_Max);
	}

	float FastRNG::NextF2()
	{
		return NextF() * 2.0f - 1.0f;
	}

	double FastRNG::NextD()
	{
		return double(NextI()) / double(NextI_Max);
	}

	double FastRNG::NextD2()
	{
		return NextD() * 2.0 - 1.0;
	}

	template <typename T> requires std::is_arithmetic_v<T>
	void FastRNG::Next(T& out)
	{
		if constexpr (std::is_same_v<T, bool>)
		{
			auto HalfMax = NextI_Max >> 1;
			out = (NextI() >= HalfMax ? true : false);
		}
		if constexpr (std::is_same_v<T, float>)
		{
			out = NextF();
		}
		else if constexpr (std::is_same_v<T, double>)
		{
			out = NextD();
		}
		else
		{
			out = T(NextI());
		}
	}

	template <IsNumberic T>
	T FastRNG::NextRange(T max)
	{
		if (max == 0) return 0;
		auto v = T(0);
		Next(v);
		if constexpr (std::is_floating_point_v<T>)
		{
			return v * max;
		}
		else
		{
			return v % max;
		}
	}

	template <IsNumberic T>
	T FastRNG::NextRange(T min, T max)
	{
		if (max < min) throw std::invalid_argument(std::string("In `FastRNG::NextRange()`: `max` (") + std::to_string(max) + ") is smaller than `min` (" + std::to_string(min) + ").");
		if (max == min) return max;

		auto diff = max - min;
		return min + NextRange(diff);
	}

	template void FastRNG::Next(int8_t& out);
	template void FastRNG::Next(int16_t& out);
	template void FastRNG::Next(int32_t& out);
	template void FastRNG::Next(int64_t& out);
	template void FastRNG::Next(uint8_t& out);
	template void FastRNG::Next(uint16_t& out);
	template void FastRNG::Next(uint32_t& out);
	template void FastRNG::Next(uint64_t& out);
	template void FastRNG::Next(bool& out);
	template void FastRNG::Next(float& out);
	template void FastRNG::Next(double& out);

	template int8_t    FastRNG::NextRange(int8_t   max);
	template int16_t   FastRNG::NextRange(int16_t  max);
	template int32_t   FastRNG::NextRange(int32_t  max);
	template int64_t   FastRNG::NextRange(int64_t  max);
	template uint8_t   FastRNG::NextRange(uint8_t  max);
	template uint16_t  FastRNG::NextRange(uint16_t max);
	template uint32_t  FastRNG::NextRange(uint32_t max);
	template uint64_t  FastRNG::NextRange(uint64_t max);
	template float     FastRNG::NextRange(float    max);
	template double    FastRNG::NextRange(double   max);

	template int8_t    FastRNG::NextRange(int8_t   min, int8_t   max);
	template int16_t   FastRNG::NextRange(int16_t  min, int16_t  max);
	template int32_t   FastRNG::NextRange(int32_t  min, int32_t  max);
	template int64_t   FastRNG::NextRange(int64_t  min, int64_t  max);
	template uint8_t   FastRNG::NextRange(uint8_t  min, uint8_t  max);
	template uint16_t  FastRNG::NextRange(uint16_t min, uint16_t max);
	template uint32_t  FastRNG::NextRange(uint32_t min, uint32_t max);
	template uint64_t  FastRNG::NextRange(uint64_t min, uint64_t max);
	template float     FastRNG::NextRange(float    min, float    max);
	template double    FastRNG::NextRange(double   min, double   max);
}
